AWS CLIからAWS WAFのWeb ACLを定義してみた #reinvent
AWS CLI 1.8.10 がリリースされ、本日発表された AWS WAF にも対応したため、早速 AWS CLI から WAF の API を叩いて、リクエストヘッダーベースのアクセス制御を行いました。
先に使った感想を申しますと CLI からの操作は過去の経験からでは route53 なみに面倒です。
CLI を使って --generate-skeleton
で入力パラメーターを確認し、--cli-input-json
でパラメーターを食わせ、API ドキュメントやエラーメッセージと睨めっとしながらの作業で何度も挫けそうになりました。
WAF オブジェクトの関係性
WAF には大きく分けて
- Web ACL
- Rule
- Condition
- Filter
の4オブジェクトがあります。
Condition は
- IP addresses
- SQL injection
- String matching
の中から種類を選べ、例えば String maching であれば
- リクエストヘッダー
- HTTP Method
- クエリーストリング
- URL の文字列
の中から Filter を複数設定出来ます。
この Condition を束ねたものが Rule です。 さらにこの Rule を束ねたものが Web ACL です
Web ACL は最終的に CloudFront のアクセスコントロールとして設定します。
この関係性を頭にいれながら API を叩きましょう。
作業の流れ
次の手順で操作します。
- AWS CLI のアップデート
- WEB ACL の作成
- Condition の作成
- Rule の作成
- CloudFront と紐付ける
- CloudFrontにアクセスをして確認
AWS WAF 更新系 API の注意点
AWS WAF オブジェクトに対する更新系(CREATE/UPDATE/DELETE) API では GetChangeToken
API でトークンを取得し、リクエストにこのトークンを含めることで、更新のコンフリクトを防いでいます。
詳細は次の API マニュアルをご確認ください。
http://docs.aws.amazon.com/waf/latest/APIReference/API_GetChangeToken.html
AWS CLI のアップデート
何はともあれ AWS CLI を 1.8.10 以上にアップデートします。
$ sudo pip install awscli --upgrade $ aws --version aws-cli/1.8.10 Python/2.7.10 Linux/4.1.7-15.23.amzn1.x86_64
waf の help コマンドを叩いて、コマンド一覧を確認しましょう。
$ aws waf help WAF() WAF() NAME waf - DESCRIPTION This is the AWS WAF API Reference . This guide is for developers who need detailed information about the AWS WAF API actions, data types, and errors. For detailed information about AWS WAF features and an overview of how to use the AWS WAF API, see the AWS WAF Developer Guide . AVAILABLE COMMANDS o create-byte-match-set o create-ip-set o create-rule o create-sql-injection-match-set o create-web-acl o delete-byte-match-set o delete-ip-set o delete-rule o delete-sql-injection-match-set o delete-web-acl o get-byte-match-set o get-change-token o get-change-token-status o get-ip-set o get-rule o get-sampled-requests o get-sql-injection-match-set o get-web-acl o help o list-byte-match-sets o list-ip-sets o list-rules o list-sql-injection-match-sets o list-web-acls o update-byte-match-set o update-ip-set o update-rule o update-sql-injection-match-set o update-web-acl
WEB ACL の作成
create-web-acl
API で WEB ACL を作成します。
$ aws waf get-change-token { "ChangeToken": "b8782710-497d-49bf-ad38-5ff225bf9aaf" } $ cat create-web-acl.json { "Name": "waftest", "MetricName": "waftest", "DefaultAction": { "Type": "ALLOW" }, "ChangeToken" : "b8782710-497d-49bf-ad38-5ff225bf9aaf" } $ aws waf create-web-acl --cli-input-json file://create-web-acl.json { "WebACL": { "DefaultAction": { "Type": "ALLOW" }, "Rules": [], "WebACLId": "0026186b-410f-46d0-b701-eb87241cdafa", "Name": "waftest" }, "ChangeToken": "b8782710-497d-49bf-ad38-5ff225bf9aaf" }
Condition の作成
WAF で制御する条件を定義します。
今回は、リクエストヘッダーの User-Agent に iPhone が含まれるクライアントのみにアクセスを許可します。 String maching ベースの Condition を作成し、この Condition に対して 上記 Filter 条件を定義します。
String maching Condition の作成には create-byte-match-set
API を利用します。
$ aws waf get-change-token { "ChangeToken": "8f9ca9c1-a21c-b6e4-962f-ef0d3b9b6019" } $ cat byte-match-set.json { "Name": "iphone-header", "ChangeToken": "8f9ca9c1-a21c-b6e4-962f-ef0d3b9b6019" } $ aws waf create-byte-match-set --cli-input-json file://byte-match-set.json { "ChangeToken": "8f9ca9c1-a21c-b6e4-962f-ef0d3b9b6019", "ByteMatchSet": { "ByteMatchSetId": "309a5b49-767d-41c0-8f4b-8fcd97ac48e5", "Name": "iphone-header", "ByteMatchTuples": [] } }
この Condition に対して Filter を追加します。
下の例の JSON ファイル update-byte-match-set.json において "ByteMatchTuple" で記述しているものが String matching ベースの Filter 条件です。
「リクエストヘッダーの User-Agent に "iPhone" が含まれる」という Filter を意味します。
先ほど作成した String matching Condition は "ByteMatchSetId" で指定します。
$ aws waf get-change-token { "ChangeToken": "f1fe1e61-683d-4b66-b115-d4cd088c90c2" } $ cat update-byte-match-set.json { "ByteMatchSetId": "309a5b49-767d-41c0-8f4b-8fcd97ac48e5", "ChangeToken": "f1fe1e61-683d-4b66-b115-d4cd088c90c2", "Updates": [ { "Action": "INSERT", "ByteMatchTuple": { "FieldToMatch": { "Data": "user-agent", "Type": "HEADER" }, "TargetString": "iPhone", "TextTransformation": "NONE", "PositionalConstraint": "CONTAINS" } } ] } $ aws waf update-byte-match-set --cli-input-json file://update-byte-match-set.json { "ChangeToken": "f1fe1e61-683d-4b66-b115-d4cd088c90c2" }
Rule の作成
今回は、リクエストヘッダーの User-Agent に iPhone が含まれるクライアントのみにアクセスを許可します。 Rule を作成し、この Rule に対して 上記 Condition を設定します。
Rule の作成には create-rule
API を利用します。
$ aws waf get-change-token { "ChangeToken": "c6d1d95c-fc6e-3de6-9409-d07f98c2a631" } $ cat create-rule.json { "Name": "waftest", "MetricName": "waftest", "ChangeToken": "c6d1d95c-fc6e-3de6-9409-d07f98c2a631" } $ aws waf create-rule --cli-input-json file://create-rule.json { "ChangeToken": "c6d1d95c-fc6e-3de6-9409-d07f98c2a631", "Rule": { "Predicates": [], "MetricName": "waftest", "Name": "waftest", "RuleId": "ec3bda6e-de6b-44a6-a835-03718aa897ad" } }
この Rule に対して update-rule
API で Condition を追加します。
下の例の JSON ファイル update-rule.json において "Updates" で記述しているものが Condition です。
先ほど作成した String matching Condition は "DataId" で指定します。 "ByteMatchSetId" ではないためお間違えの内容にお願いします。
$ aws waf get-change-token { "ChangeToken": "0914f17c-bd7a-4d01-b302-b4e37b4c7fb0" } $ cat update-rule.json { "RuleId": "ec3bda6e-de6b-44a6-a835-03718aa897ad", "ChangeToken": "0914f17c-bd7a-4d01-b302-b4e37b4c7fb0", "Updates": [ { "Action": "INSERT", "Predicate": { "Negated": true, "Type": "ByteMatch", "DataId": "309a5b49-767d-41c0-8f4b-8fcd97ac48e5" } } ] } $ aws waf update-rule --cli-input-json file://update-rule.json { "ChangeToken": "0914f17c-bd7a-4d01-b302-b4e37b4c7fb0" }
この Rule を update-web-acl
API で作成済み Web ACL に紐付けると WAF に閉じた設定は完了です。
下の JSON ファイル update-web-acl.json において "Updates" で記述しているものが Rule です。 Rule を "Type" : "ALLOW" で追加し、"DefaultAction" では "Type": "BLOCK" とします。 こうすることで、 Rule を満たすアクセスは許可され、満たさないアクセスは許可されなくなります。
先ほど作成した Rule は "RuleId" で指定します。
$ aws waf get-change-token { "ChangeToken": "43cd5ea5-ac95-246e-ae21-9e96c3536192" } $ cat update-web-acl.json { "WebACLId": "0026186b-410f-46d0-b701-eb87241cdafa", "ChangeToken": "43cd5ea5-ac95-246e-ae21-9e96c3536192", "Updates": [ { "Action": "INSERT", "ActivatedRule": { "Priority": 1, "RuleId" : "ec3bda6e-de6b-44a6-a835-03718aa897ad", "Action": { "Type": "ALLOW" } } } ], "DefaultAction": { "Type": "BLOCK" } } $ aws waf update-web-acl --cli-input-json file://update-web-acl.json { "ChangeToken": "43cd5ea5-ac95-246e-ae21-9e96c3536192" }
更新した Web ACL を get-web-acl
API で確認してみましょう。
$ aws waf get-web-acl --web-acl-id "0026186b-410f-46d0-b701-eb87241cdafa" { "WebACL": { "DefaultAction": { "Type": "BLOCK" }, "Rules": [ { "Priority": 1, "Action": { "Type": "ALLOW" }, "RuleId": "ec3bda6e-de6b-44a6-a835-03718aa897ad" } ], "WebACLId": "0026186b-410f-46d0-b701-eb87241cdafa", "Name": "waftest" } }
CloudFront と紐付ける
CloudFront の編集画面で、先ほど作成した AWS WAF Web ACL を設定します。
アクセスをして確認
それでは cloudfront に GET リクエストを投げてみましょう。 まずはリクエストヘッダーをカスタマイズせずにアクセスします。
$ curl DUMMY.cloudfront.net <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> <TITLE>ERROR: The request could not be satisfied</TITLE> </HEAD><BODY> <H1>ERROR</H1> <H2>The request could not be satisfied.</H2> <hr noshade size="1px"> Request blocked. <BR clear="all"> <hr noshade size="1px"> <PRE> Generated by cloudfront (CloudFront) Request ID: VeeVtOdUprDz2wfIx3gozmorZvcPnthm8dZj4cyvBLvSdxw3jHaT3A== </PRE> <ADDRESS> </ADDRESS> </BODY></HTML>
Web ACL Rule に従って "ERROR: The request could not be satisfied" などとエラーメッセージが表示されていますね。
次に User-Agent に iPhone の文字を含めてアクセスしてみましょう。
$ curl --header "user-agent: iPhone" DUMMY.cloudfront.net <body> aws waf test </body>
CloudFront がエラーを返さなくなりました。
まとめ
長い道のりでしたが CLI でゴリゴリと WAF 設定出来ました。
カジュアルに使うのであれば、マネージメントコンソールから画面ポチポチするのが素直ですが、設定の再現性やまとまった処理をするのであれば CLI や SDK が良いでしょう。
関連記事
マネージメントコンソールから特定のUser-Agentだけ通信させる方法については次の記事を参照ください。
AWS WAFで特定のUser-Agentだけ通信できるサイトを構築 #reinvent | Developers.IO
AWS WAF の API 一覧については本日の次の記事を参照ください。